YAML Validation in WOLF
WOLF uses the yaml-schema-cpp library to validate YAML input files against predefined schema files. This ensures that the YAML configuration files provided by the user are well-structured, contain all mandatory fields, and adhere to the expected data types and constraints.
The validation process guarantees that the YAML input is correct and frees the developer to implement extra validation.
The .schema files
The specification of the input YAML file is performed via one or more schema files.
Important
The schema file should have the same name as the class.
All schema files should be stored under the schema folder in order to be installed.
It is mandatory to create a schema for each new class using autoconf (Sensor, Processor, Landmark, Map, TreeManager, Solver, Subscriber and Publisher).
The schema file will define:
Structure and fields: The schema file will contain the same structure (maps) as the expected input. Only the parameters (children nodes) are specified.
For each parameter, the schema will specify:
_doc: Brief documentation of the parameter for the user.
_type: Type of the parameter allowing custom classes. For trivial cases (bool, int, float, string, etc.) a basic type check will be performed. For custom classes, the corresponding schema file will be loaded to validate the user input.
_mandatory: Whether the parameter is mandatory or optional.
_default(optional): In case of optional parameter, default values can be specified.
_value(optional): Set the value of a parameter (requires_mandatory: false) and complains if different.
_options(optional): The values of a parameter can be limited to a set of options.
_base(optional): To use of polymorfism in type checking, you can specify_type: derived, and specify a base class to perform a preliminar check. The user will have to specify the derived type.
Including other schemas
You can include the content of another schema file using follow key. This is specially useful
when deriving a class: The base class will require some parameters and the derived one will add some
extra specification.
Tip
A derived class schema should normally start with: follow: BaseClassName.schema.
Expressions
You can make use of boolean expressions starting with a $. For example: _mandatory: $param_1>0 will
define if a parameter is mandatory depending on another parameter param_1.
See also
Read the yaml-schema-cpp documentation for more information.
For example, the schema file SensorDiffDrive.schema specifies the required fields for a SensorDiffDrive configuration:
follow: SensorBase.schema
ticks_per_wheel_revolution:
_mandatory: true
_type: double
_doc: Amount of sensor ticks of a whole wheel revolution (if measurement is directly radiants, put 2*PI).
ticks_std_factor:
_mandatory: true
_type: double
_doc: ratio of displacement variance to rotation, for odometry noise calculation.
states:
P:
follow: StateSensorP2d.schema
O:
follow: StateSensorO2d.schema
I:
dynamic:
_type: bool
_mandatory: true
_doc: "If the intrinsic state is dynamic, i.e. it changes along time (0: left wheel radius [m], 1: right wheel radius [m], 2: wheel separation [m])."
value:
_type: Vector3d
_mandatory: true
_doc: "A vector containing the intrinsic state values (0: left wheel radius [m], 1: right wheel radius [m], 2: wheel separation [m])."
prior:
mode:
_type: string
_mandatory: true
_options: ["fix", "factor", "initial_guess"]
_doc: "Can be 'factor' to add an absolute factor (with covariance defined in 'factor_std'), 'fix' to set the values constant or 'initial_guess' to just set the values."
factor_std:
_type: Vector3d
_mandatory: $mode == 'factor'
_doc: "A vector containing the stdev values of the noise of the factor, i.e. the sqrt of the diagonal elements of the covariance matrix [m]."
drift_std:
_type: Vector3d
_mandatory: false
_doc: "A vector containing the stdev values of the noise of the drift factor per second (only if dynamic==true) i.e. the sqrt of the diagonal elements of the covariance matrix [m/sqrt(s)]."
YAML Validation
The validation process involves:
Parsing the YAML input file into a YAML::Node.
Searching and loading the schema file(s).
Applying the corrsponding schema(s) to the YAML node to check for compliance and complement it with default values.
The following snippet is a toy example on how to validate a YAML file:
std::string yaml_filepath = "/path/to/input/file.yaml"
std::vector<std::string> folders_schema = {"/path1/containing/installed/schemas", "path2/with/more/schemas"};
auto server = yaml_schema_cpp::YamlServer(folders_schema, yaml_filepath);
if (not server.applySchema("my_specification.schema"))
{
WOLF_ERROR(server.getLog());
return nullptr;
}
The YamlServer object loads the schema files from the specified folders and validates the YAML node against the specified schema using applySchema.
The validation is typically performed in the create methods of components (e.g., sensors, processors) using the WOLF_SENSOR_CREATE macro.
If the YAML file does not comply with the schema, the validation process logs detailed error messages and stops further execution.
YAML Templates
Another feature of the yaml-schema-cpp library is the YAML template generation. From schema files, a YAML file is generated containing the structure and all parameters specified with dummy values and including the doc in comments.
In the WOLF framework, YAML templates for all schema files are automatically generated during the GitLab CI pipeline, and stored in the folder yaml_templates.
This ensures that users have access to up-to-date YAML templates that comply with the schema definitions, making it easier to create valid YAML configuration files.
For example, the generated template for SubscriberOdom2d might look like this:
type: "whatever" # DOC The class name - TYPE string
package: "whatever" # DOC ROS package where the class is implemented - TYPE string
topic: "whatever" # DOC Topic of the ROS publisher/subscriber - TYPE std::string
sensor_name: "whatever" # DOC Name of the WOLF sensor corresponding to this subscriber - TYPE std::string
odom_from_pose: false # DOC If the odometry has to be computed from the field "pose" of consecutive messages. If false, it will be computed multiplying "twist" by the time between consecutive messages. - TYPE bool